﻿using System;
using System.Collections.Generic;
using Aeon.Emulator.DebugSupport;
using Aeon.Emulator.Decoding;
using Aeon.Emulator.Dos.Programs;
using Aeon.Emulator.Dos.VirtualFileSystem;
using Aeon.Emulator.Memory;
using Aeon.Emulator.RuntimeExceptions;
using Aeon.Emulator.Interrupts;

namespace Aeon.Emulator
{
    /// <summary>
    /// Emulates the functions of an x86 system.
    /// </summary>
    public class VirtualMachine : IDisposable, IMachineCodeSource
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the VirtualMachine class.
        /// </summary>
        public VirtualMachine()
        {
            this.stack16 = SystemStack.Get16BitStack(this);
            this.stack32 = SystemStack.Get32BitStack(this);
            this.stack = this.stack16;

            this.keyboard = new Keyboard.KeyboardDevice();
            this.consoleInStream = new ConsoleInStream(this.keyboard);
            this.video = new Video.VideoHandler(this);
            this.consoleOutStream = new ConsoleOutStream(this.video.TextConsole);
            this.dos = new Dos.DosHandler(this);
            this.mouse = new Mouse.MouseHandler();
            this.InterruptTimer = new InterruptTimer();
            this.emm = new Memory.ExpandedMemoryManager();
            this.xmm = new Memory.ExtendedMemoryManager();
            this.dma = new DmaController(this);
            this.console = new VirtualConsole(this);
            this.multiplexHandler = new Interrupts.MultiplexInterruptHandler();
            this.PhysicalMemory.Video = this.video;

            this.dos.InitializationComplete();

            RegisterVirtualDevice(this.dos);
            RegisterVirtualDevice(this.video);
            RegisterVirtualDevice(this.keyboard);
            RegisterVirtualDevice(this.mouse);
            RegisterVirtualDevice(new Interrupts.RealTimeClockHandler());
            RegisterVirtualDevice(new Interrupts.ErrorHandler());
            RegisterVirtualDevice(this.InterruptController);
            RegisterVirtualDevice(this.InterruptTimer);
            RegisterVirtualDevice(this.emm);
            RegisterVirtualDevice(this.dma);
            RegisterVirtualDevice(this.multiplexHandler);
            RegisterVirtualDevice(new BiosServices.SystemServices());
            RegisterVirtualDevice(this.xmm);
            RegisterVirtualDevice(new Dos.CD.Mscdex());
            RegisterVirtualDevice(new LowLevelDisk.LowLevelDiskInterface());

            this.PhysicalMemory.AddTimerInterruptHandler();
        }
        #endregion

        #region Public Events
        /// <summary>
        /// Occurs when the emulated display mode has changed.
        /// </summary>
        public event EventHandler VideoModeChanged;
        /// <summary>
        /// Occurs when the emulator sets the mouse position.
        /// </summary>
        public event EventHandler<MouseMoveEventArgs> MouseMoveByEmulator;
        /// <summary>
        /// Occurs when the internal mouse position has changed.
        /// </summary>
        public event EventHandler<MouseMoveEventArgs> MouseMove;
        /// <summary>
        /// Occurs when the mouse cursor is shown or hidden.
        /// </summary>
        public event EventHandler MouseVisibilityChanged;
        /// <summary>
        /// Occurs when the text-mode cursor is shown or hidden.
        /// </summary>
        public event EventHandler CursorVisibilityChanged;
        /// <summary>
        /// Occurs when the current process has changed.
        /// </summary>
        public event EventHandler CurrentProcessChanged;
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the virtual file system used by the virtual machine.
        /// </summary>
        public FileSystem FileSystem
        {
            get { return fileSystem; }
        }
        /// <summary>
        /// Gets information about the current emulated video mode.
        /// </summary>
        public Video.VideoMode VideoMode
        {
            get
            {
                var video = this.video;
                if(video != null)
                    return video.CurrentMode;
                else
                    return null;
            }
        }
        /// <summary>
        /// Gets a collection of all virtual devices registered with the virtual machine.
        /// </summary>
        public IEnumerable<IVirtualDevice> Devices
        {
            get { return allDevices.AsReadOnly(); }
        }
        /// <summary>
        /// Gets a value indicating whether the mouse cursor should be displayed.
        /// </summary>
        public bool IsMouseVisible
        {
            get
            {
                return showMouse;
            }
            internal set
            {
                if(showMouse != value)
                {
                    showMouse = value;
                    OnMouseVisibilityChanged(EventArgs.Empty);
                }
            }
        }
        /// <summary>
        /// Gets the current position of the mouse.
        /// </summary>
        public Video.Point MousePosition
        {
            get { return mouse.Position; }
        }
        /// <summary>
        /// Gets the current DOS environment variables.
        /// </summary>
        public EnvironmentVariables EnvironmentVariables
        {
            get { return environmentVariables; }
        }
        /// <summary>
        /// Gets the DMA controller for the virtual machine.
        /// </summary>
        public DmaController DmaController
        {
            get { return dma; }
        }
        /// <summary>
        /// Gets the current position of the cursor.
        /// </summary>
        public Video.Point CursorPosition
        {
            get
            {
                var console = this.video.TextConsole;
                if(console != null)
                    return console.CursorPosition;
                else
                    return new Video.Point();
            }
        }
        /// <summary>
        /// Gets a value indicating whether the text-mode cursor is visible.
        /// </summary>
        public bool IsCursorVisible
        {
            get
            {
                return showCursor;
            }
            internal set
            {
                if(showCursor != value)
                {
                    showCursor = value;
                    OnCursorVisibilityChanged(EventArgs.Empty);
                }
            }
        }
        /// <summary>
        /// Gets the stream used for writing data to the emulated console.
        /// </summary>
        public ConsoleOutStream ConsoleOut
        {
            get { return this.consoleOutStream; }
        }
        /// <summary>
        /// Gets the stream used for reading data from the emulated keyboard.
        /// </summary>
        public ConsoleInStream ConsoleIn
        {
            get { return this.consoleInStream; }
        }
        /// <summary>
        /// Gets the console associated with the virtual machine.
        /// </summary>
        public VirtualConsole Console
        {
            get { return this.console; }
        }
        /// <summary>
        /// Gets information about the current process.
        /// </summary>
        public Dos.DosProcess CurrentProcess
        {
            get { return this.dos.CurrentProcess; }
        }
        #endregion

        #region Internal Properties
        /// <summary>
        /// Gets the emulated keyboard device.
        /// </summary>
        internal Keyboard.KeyboardDevice Keyboard
        {
            get { return keyboard; }
        }
        /// <summary>
        /// Gets the emulated mouse device.
        /// </summary>
        internal Mouse.MouseHandler Mouse
        {
            get { return mouse; }
        }
        /// <summary>
        /// Gets the emulated DOS environment.
        /// </summary>
        internal Dos.DosHandler Dos
        {
            get { return this.dos; }
        }
        /// <summary>
        /// Gets the emulated video device.
        /// </summary>
        internal Video.VideoHandler Video
        {
            get { return this.video; }
        }
        /// <summary>
        /// Gets or sets a value indicating whether the stack pointer is 32-bits.
        /// </summary>
        internal bool BigStackPointer
        {
            get { return object.ReferenceEquals(this.stack, this.stack32); }
            set { this.stack = value ? this.stack32 : this.stack16; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Loads an executable file into the virtual machine.
        /// </summary>
        /// <param name="image">Executable file to load.</param>
        public void LoadImage(ProgramImage image)
        {
            if(image == null)
                throw new ArgumentNullException("image");

            dos.LoadImage(image);
        }
        /// <summary>
        /// Loads an executable file into the virtual machine.
        /// </summary>
        /// <param name="image">Executable file to load.</param>
        /// <param name="commandLineArguments">Command line arguments for the program.</param>
        public void LoadImage(ProgramImage image, string commandLineArguments)
        {
            if(image == null)
                throw new ArgumentNullException("image");
            if(commandLineArguments != null && commandLineArguments.Length > 255)
                throw new ArgumentException("Command line length must not exceed 255 characters.", "commandLineArguments");

            dos.LoadImage(image, commandLineArguments);
        }
        /// <summary>
        /// Raises a hardware/software interrupt on the virtual machine.
        /// </summary>
        /// <param name="interrupt">Interrupt to raise.</param>
        public void RaiseInterrupt(byte interrupt)
        {
            if((this.Processor.CR0 & CR0.ProtectedModeEnable) == 0)     // Real mode
            {
                var address = PhysicalMemory.GetRealModeInterruptAddress(interrupt);
                if(address.Segment == 0 && address.Offset == 0)
                {
                    System.Diagnostics.Debug.WriteLine("Unhandled real-mode interrupt");
                    return;
                }

                PushToStack((ushort)Processor.Flags);
                PushToStack(Processor.CS);
                PushToStack(Processor.IP);

                Processor.EIP = address.Offset;
                WriteSegmentRegister(SegmentIndex.CS, address.Segment);

                this.Processor.Flags &= ~EFlags.Trap;
                this.Processor.Flags &= ~EFlags.InterruptEnable;
            }
            else        // Protected mode
            {
                var descriptor = this.PhysicalMemory.GetInterruptDescriptor(interrupt);
                if(descriptor.DescriptorType == DescriptorType.InterruptGate || descriptor.DescriptorType == DescriptorType.TrapGate)
                {
                    var interruptGate = (InterruptDescriptor)descriptor;
                    uint wordSize = interruptGate.Is32Bit ? 4u : 2u;

                    uint cpl = this.Processor.CS & 3u;
                    uint rpl = interruptGate.Selector & 3u;

                    if(cpl > rpl)
                    {
                        ushort oldSS = this.Processor.SS;
                        uint oldESP = this.Processor.ESP;

                        ushort newSS = this.GetPrivilegedSS(rpl, wordSize);
                        uint newESP = this.GetPrivilegedESP(rpl, wordSize);

                        WriteSegmentRegister(SegmentIndex.SS, newSS);
                        this.Processor.ESP = newESP;

                        if(wordSize == 4u)
                        {
                            PushToStack32(oldSS);
                            PushToStack32(oldESP);
                        }
                        else
                        {
                            PushToStack(oldSS);
                            PushToStack((ushort)oldESP);
                        }
                    }
                    else if(cpl < rpl)
                        throw new InvalidOperationException();

                    if(wordSize == 4u)
                    {
                        PushToStack32((uint)this.Processor.Flags);
                        PushToStack32(this.Processor.CS);
                        PushToStack32(this.Processor.EIP);
                    }
                    else
                    {
                        PushToStack((ushort)this.Processor.Flags);
                        PushToStack(this.Processor.CS);
                        PushToStack(this.Processor.IP);
                    }

                    this.Processor.EIP = interruptGate.Offset;
                    WriteSegmentRegister(SegmentIndex.CS, interruptGate.Selector);

                    // Disable interrupts if not a trap.
                    if(!interruptGate.IsTrap)
                        this.Processor.Flags &= ~EFlags.InterruptEnable;
                }
                else
                {
                    var desc = (InterruptDescriptor)descriptor;
                    TaskSwitch32(desc.Selector, false, true);
                }
            }
        }
        /// <summary>
        /// Emulates the next instruction.
        /// </summary>
        public void Emulate()
        {
            InstructionSet.Emulate(this);
        }
        /// <summary>
        /// Emulates multiple instructions.
        /// </summary>
        /// <param name="count">Number of instructions to emulate.</param>
        public virtual void Emulate(int count)
        {
            for(; count > 0; count--)
                Emulate();
        }
        /// <summary>
        /// Emulates the next instruction with logging enabled.
        /// </summary>
        /// <param name="log">Log to which instructions will be written.</param>
        [CLSCompliant(false)]
        public void Emulate(DebugSupport.InstructionLog log)
        {
            InstructionSet.Emulate(this, log);
        }
        /// <summary>
        /// Presses a key on the emulated keyboard.
        /// </summary>
        /// <param name="key">Key to press.</param>
        public void PressKey(Keys key)
        {
            var process = this.CurrentProcess;
            if(process != null)
                this.keyboard.PressKey(key);
        }
        /// <summary>
        /// Releases a key on the emulated keyboard.
        /// </summary>
        /// <param name="key">Key to release.</param>
        public void ReleaseKey(Keys key)
        {
            var process = this.CurrentProcess;
            if(process != null)
                this.keyboard.ReleaseKey(key);
        }
        /// <summary>
        /// Notifies the virtual machine that a keyboard ISR is about to be invoked.
        /// </summary>
        /// <remarks>
        /// This must be called once before IRQ 1 is handled.
        /// </remarks>
        public void PrepareForKeyboardHandler()
        {
            keyboard.BeginHardwareInterrupt();
        }
        /// <summary>
        /// Signals that a mouse input event has occurred.
        /// </summary>
        /// <param name="mouseEvent">Mouse input event that occurred.</param>
        public void MouseEvent(MouseEvent mouseEvent)
        {
            if(mouseEvent == null)
                throw new ArgumentNullException("mouseEvent");

            mouseEvent.RaiseEvent(mouse);
        }
        /// <summary>
        /// Registers a virtual device with the virtual machine.
        /// </summary>
        /// <param name="virtualDevice">Virtual device to register.</param>
        public void RegisterVirtualDevice(IVirtualDevice virtualDevice)
        {
            if(virtualDevice == null)
                throw new ArgumentNullException("virtualDevice");

            var interruptHandler = virtualDevice as IInterruptHandler;
            if(interruptHandler != null)
            {
                foreach(var interrupt in interruptHandler.HandledInterrupts)
                {
                    interruptHandlers[interrupt.Interrupt] = interruptHandler;
                    PhysicalMemory.AddInterruptHandler((byte)interrupt.Interrupt, interrupt.SavedRegisters, interrupt.IsHookable, interrupt.ClearInterruptFlag);
                }
            }

            var multiplex = virtualDevice as IMultiplexInterruptHandler;
            if(multiplex != null)
                this.multiplexHandler.Handlers.Add(multiplex);

            var inputPort = virtualDevice as IInputPort;
            if(inputPort != null)
            {
                foreach(int port in inputPort.InputPorts)
                    inputPorts[(uint)port] = inputPort;
            }

            var outputPort = virtualDevice as IOutputPort;
            if(outputPort != null)
            {
                foreach(int port in outputPort.OutputPorts)
                    outputPorts[(uint)port] = outputPort;
            }

            var dmaDevice = virtualDevice as IDmaDevice8;
            if(dmaDevice != null)
            {
                if(dmaDevice.Channel < 0 || dmaDevice.Channel >= dma.Channels.Count)
                    throw new ArgumentException("Invalid DMA channel on DMA device.");

                dma.Channels[dmaDevice.Channel].Device = dmaDevice;
                dmaDeviceChannels.Add(dma.Channels[dmaDevice.Channel]);
            }

            var callbackProvider = virtualDevice as ICallbackProvider;
            if(callbackProvider != null)
            {
                int id = callbackProviders.Count;
                callbackProvider.CallbackAddress = this.PhysicalMemory.AddCallbackHandler((byte)id, callbackProvider.IsHookable);
                callbackProviders.Add((uint)id, callbackProvider);
            }

            allDevices.Add(virtualDevice);
            virtualDevice.DeviceRegistered(this);
        }
        /// <summary>
        /// Informs the VirtualMachine instance that initialization is complete and no more devices will be added.
        /// This must be called prior to emulation.
        /// </summary>
        public void EndInitialization()
        {
            this.PhysicalMemory.ReserveBaseMemory();
        }
        /// <summary>
        /// Returns an object containing information about current conventional memory usage.
        /// </summary>
        /// <returns>Information about current conventional memory usage.</returns>
        public ConventionalMemoryInfo GetConventionalMemoryUsage()
        {
            return dos.GetAllocations();
        }
        /// <summary>
        /// Returns an object containing information about current expanded memory usage.
        /// </summary>
        /// <returns>Information about current expanded memory usage.</returns>
        public ExpandedMemoryInfo GetExpandedMemoryUsage()
        {
            return new ExpandedMemoryInfo(emm.AllocatedPages);
        }
        /// <summary>
        /// Returns an object containing information about current extended memory usage.
        /// </summary>
        /// <returns>Information about current extended memory usage.</returns>
        public ExtendedMemoryInfo GetExtendedMemoryUsage()
        {
            return new ExtendedMemoryInfo(xmm.ExtendedMemorySize - (int)xmm.TotalFreeMemory, xmm.ExtendedMemorySize);
        }
        /// <summary>
        /// Performs any pending DMA transfers.
        /// </summary>
        /// <remarks>
        /// This method must be called frequently in the main emulation loop for DMA transfers to function properly.
        /// </remarks>
        public void PerformDmaTransfers()
        {
            foreach(var channel in this.dmaDeviceChannels)
            {
                if(channel.IsActive && !channel.IsMasked)
                    channel.Transfer(this.PhysicalMemory);
            }
        }
        /// <summary>
        /// Raises the appropriate interrupt handler for a runtime exception if possible.
        /// </summary>
        /// <param name="exception">Runtime exception to raise.</param>
        /// <returns>Value indicating whether exception was handled.</returns>
        public bool RaiseException(EmulatedException exception)
        {
            if(exception == null)
                throw new ArgumentNullException("exception");

            System.Diagnostics.Debug.WriteLine(exception.Message);

            if(this.Processor.PrefixCount > 0)
            {
                this.Processor.EIP = this.Processor.StartEIP - (uint)this.Processor.PrefixCount;
                this.Processor.InstructionEpilog();
            }
            else
                this.Processor.EIP = this.Processor.StartEIP;

            exception.OnRaised(this);

            if(exception.Interrupt >= 0 && exception.Interrupt <= 255)
            {
                this.RaiseInterrupt((byte)exception.Interrupt);
                if(exception.ErrorCode != null)
                {
                    bool is32Bit;
                    var descriptor = this.PhysicalMemory.GetInterruptDescriptor((byte)exception.Interrupt);
                    if(descriptor.DescriptorType == DescriptorType.InterruptGate)
                        is32Bit = ((InterruptDescriptor)descriptor).Is32Bit;
                    else if(descriptor.DescriptorType == DescriptorType.TaskGate)
                        is32Bit = true;
                    else
                        throw new NotImplementedException();
     
                    if(is32Bit)
                        PushToStack32((uint)(int)exception.ErrorCode);
                    else
                        PushToStack((ushort)(int)exception.ErrorCode);
                }

                return true;
            }
            else
                return false;
        }
        /// <summary>
        /// Releases resources used during emulation.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Returns the logical base address for a given selector.
        /// </summary>
        /// <param name="selector">Selector whose base address is returned.</param>
        /// <returns>Base address of the selector if it is valid; otherwise null.</returns>
        uint? IMachineCodeSource.GetBaseAddress(ushort selector)
        {
            if((this.Processor.CR0 & CR0.ProtectedModeEnable) == 0)
                return (uint)selector << 4;
            else if(selector != 0)
            {
                var descriptor = (SegmentDescriptor)this.PhysicalMemory.GetDescriptor(selector);
                if(descriptor.Base < this.PhysicalMemory.MemorySize)
                    return descriptor.Base;
            }

            return null;
        }
        /// <summary>
        /// Reads 16 bytes of data from the machine code source at the specified address.
        /// </summary>
        /// <param name="buffer">Buffer into which data is read. Must be at least 16 bytes long.</param>
        /// <param name="logicalAddress">Logical address in machine code source where instruction is read from.</param>
        /// <returns>Number of bytes actually read. Should normally return 16.</returns>
        int IMachineCodeSource.ReadInstruction(byte[] buffer, uint logicalAddress)
        {
            unsafe
            {
                fixed(byte* ptr = buffer)
                {
                    this.PhysicalMemory.FetchInstruction(logicalAddress, ptr);
                }
            }

            return 16;
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Releases resources used during emulation.
        /// </summary>
        /// <param name="disposing">True indicates that this method was invoked from Dispose; false if it was invoked from the finalizer.</param>
        protected virtual void Dispose(bool disposing)
        {
            if(!disposed && disposing)
            {
                foreach(var device in allDevices)
                {
                    if(device != null)
                        device.Dispose();
                }

                allDevices.Clear();
                this.PhysicalMemory.Dispose();

                disposed = true;
            }
        }
        /// <summary>
        /// Raises the VideoModeChanged event.
        /// </summary>
        /// <param name="e">Empty EventArgs instance.</param>
        protected internal virtual void OnVideoModeChanged(EventArgs e)
        {
            var modeChanged = this.VideoModeChanged;
            if(modeChanged != null)
                modeChanged(this, e);

            var mode = this.VideoMode;
            if(mode != null)
                this.IsCursorVisible = mode.HasCursor;
        }
        /// <summary>
        /// Raises the MouseMoveByEmulator event.
        /// </summary>
        /// <param name="e">MouseMoveEventArgs instance with information about the mouse movement.</param>
        protected internal virtual void OnMouseMoveByEmulator(MouseMoveEventArgs e)
        {
            var moveByEmulator = this.MouseMoveByEmulator;
            if(moveByEmulator != null)
                moveByEmulator(this, e);
        }
        /// <summary>
        /// Raises the MouseMove event.
        /// </summary>
        /// <param name="e">MouseMoveEventArgs instance with information about the mouse movement.</param>
        protected internal virtual void OnMouseMove(MouseMoveEventArgs e)
        {
            var move = this.MouseMove;
            if(move != null)
                move(this, e);
        }
        /// <summary>
        /// Raises the MouseVisibilityChanged event.
        /// </summary>
        /// <param name="e">Empty EventArgs instance.</param>
        protected virtual void OnMouseVisibilityChanged(EventArgs e)
        {
            var visibilityChanged = this.MouseVisibilityChanged;
            if(visibilityChanged != null)
                visibilityChanged(this, e);
        }
        /// <summary>
        /// Raises the CursorVisibilityChanged event.
        /// </summary>
        /// <param name="e">Empty EventArgs instance.</param>
        protected virtual void OnCursorVisibilityChanged(EventArgs e)
        {
            var visibilityChanged = this.CursorVisibilityChanged;
            if(visibilityChanged != null)
                visibilityChanged(this, e);
        }
        /// <summary>
        /// Raises the CurrentProcessChanged event.
        /// </summary>
        /// <param name="e">Empty EventArgs instance.</param>
        protected internal virtual void OnCurrentProcessChanged(EventArgs e)
        {
            var processChanged = this.CurrentProcessChanged;
            if(processChanged != null)
                processChanged(this, e);
        }
        #endregion

        #region Internal Methods
        /// <summary>
        /// Runs the handler associated with an interrupt.
        /// </summary>
        /// <param name="interrupt">Interrupt to raise.</param>
        internal void CallInterruptHandler(byte interrupt)
        {
            IInterruptHandler handler = interruptHandlers[interrupt];
            if(handler != null)
                handler.HandleInterrupt(interrupt);
            else
                throw new ArgumentException("There is no handler associated with the interrupt.", "interrupt");
        }
        /// <summary>
        /// Runs the handler associated with a callback ID.
        /// </summary>
        /// <param name="id">ID of callback.</param>
        internal void CallCallback(byte id)
        {
            ICallbackProvider provider;

            try
            {
                provider = this.callbackProviders[id];
            }
            catch(KeyNotFoundException ex)
            {
                throw new ArgumentException("There is no handler associated with the callback ID.", "id", ex);
            }

            provider.InvokeCallback();
        }
        /// <summary>
        /// Pushes a word onto the stack.
        /// </summary>
        /// <param name="value">Word to push onto the stack.</param>
        internal void PushToStack(ushort value)
        {
            this.stack.PushUInt16(value);
        }
        /// <summary>
        /// Pushes a doubleword onto the stack.
        /// </summary>
        /// <param name="value">Doubleword to push onto the stack.</param>
        internal void PushToStack32(uint value)
        {
            this.stack.PushUInt32(value);
        }
        /// <summary>
        /// Pops a word from the stack.
        /// </summary>
        /// <returns>Word popped from the stack.</returns>
        internal ushort PopFromStack()
        {
            return this.stack.PopUInt16();
        }
        /// <summary>
        /// Pops a doubleword from the stack.
        /// </summary>
        /// <returns>Doubleword popped from the stack.</returns>
        internal uint PopFromStack32()
        {
            return this.stack.PopUInt32();
        }
        /// <summary>
        /// Adds a value to the stack pointer.
        /// </summary>
        /// <param name="value">Value to add to the stack pointer.</param>
        internal void AddToStackPointer(uint value)
        {
            this.stack.Add(value);
        }
        /// <summary>
        /// Returns the top 16 bits on the stack.
        /// </summary>
        /// <returns>The top 16 bits on the stack.</returns>
        internal ushort PeekStack16()
        {
            return this.stack.PeekUInt16();
        }
        /// <summary>
        /// Returns the top 32 bits on the stack.
        /// </summary>
        /// <returns>The top 32 bits on the stack.</returns>
        internal uint PeekStack32()
        {
            return this.stack.PeekUInt32();
        }
        /// <summary>
        /// Returns the top 48 bits on the stack.
        /// </summary>
        /// <returns>The top 48 bits on the stack.</returns>
        internal ulong PeekStack48()
        {
            return this.stack.PeekUInt48();
        }
        /// <summary>
        /// Returns a byte value from the specified port.
        /// </summary>
        /// <param name="port">Port to read.</param>
        /// <returns>Byte read from the port.</returns>
        internal byte ReadPortByte(uint port)
        {
            IInputPort inputPort;
            if(inputPorts.TryGetValue(port, out inputPort))
                return inputPort.ReadByte((int)port);
            else
                return defaultPortHandler.ReadByte((int)port);
        }
        /// <summary>
        /// Returns a word value from the specified port.
        /// </summary>
        /// <param name="port">Port to read.</param>
        /// <returns>Word read from the port.</returns>
        internal ushort ReadPortWord(uint port)
        {
            IInputPort inputPort;
            if(inputPorts.TryGetValue(port, out inputPort))
                return inputPort.ReadWord((int)port);
            else
                return 0xFFFF;
        }
        /// <summary>
        /// Writes a byte value to a port.
        /// </summary>
        /// <param name="port">Port to write to.</param>
        /// <param name="value">Value to write to port.</param>
        internal void WritePortByte(uint port, byte value)
        {
            IOutputPort outputPort;
            if(!outputPorts.TryGetValue(port, out outputPort))
                defaultPortHandler.WriteByte((int)port, value);
            else
                outputPort.WriteByte((int)port, value);
        }
        /// <summary>
        /// Writes a word value to a port.
        /// </summary>
        /// <param name="port">Port to write to.</param>
        /// <param name="value">Value to write to port.</param>
        internal void WritePortWord(uint port, ushort value)
        {
            IOutputPort outputPort;
            if(!outputPorts.TryGetValue(port, out outputPort))
                defaultPortHandler.WriteWord((int)port, value);
            else
                outputPort.WriteWord((int)port, value);
        }
        /// <summary>
        /// Writes a value to a segment register.
        /// </summary>
        /// <param name="segment">Index of the segment.</param>
        /// <param name="value">Value to write to the segment register.</param>
        /// <remarks>
        /// This method should be called any time a segment register is changed instead of
        /// setting the segment register on the processor directly. This method also updates
        /// the precalculated base address for the segment.
        /// </remarks>
        [CLSCompliant(false)]
        public void WriteSegmentRegister(SegmentIndex segment, ushort value)
        {
            ushort oldValue;
            unsafe
            {
                oldValue = *this.Processor.segmentRegisterPointers[(int)segment];
                *this.Processor.segmentRegisterPointers[(int)segment] = value;
            }

            try
            {
                UpdateSegment(segment);
            }
            catch(SegmentNotPresentException)
            {
                unsafe
                {
                    *this.Processor.segmentRegisterPointers[(int)segment] = oldValue;
                }
                throw;
            }
            catch(GeneralProtectionFaultException)
            {
                unsafe
                {
                    *this.Processor.segmentRegisterPointers[(int)segment] = oldValue;
                }
                throw;
            }
        }
        /// <summary>
        /// Notifies the virtual machine of a change to a segment register value.
        /// </summary>
        /// <param name="segment">Index of the segment.</param>
        internal void UpdateSegment(SegmentIndex segment)
        {
            unsafe
            {
                ushort value = *this.Processor.segmentRegisterPointers[(int)segment];

                if((this.Processor.CR0 & CR0.ProtectedModeEnable) == 0)
                {
                    this.Processor.segmentBases[(int)segment] = (uint)value << 4;
                    this.BigStackPointer = false;
                }
                else
                {
                    var descriptor = this.PhysicalMemory.GetDescriptor(value);
                    if(value != 0 && descriptor.DescriptorType != DescriptorType.Segment)
                        throw new GeneralProtectionFaultException(value);

                    var segmentDescriptor = (SegmentDescriptor)descriptor;

                    if(value > 3u && !segmentDescriptor.IsPresent)
                    {
                        if((value & 0x4u) == 0)
                            throw new GDTSegmentNotPresentException((uint)value >> 3);
                        else
                            throw new LDTSegmentNotPresentException((uint)value >> 3);
                    }

                    this.Processor.segmentBases[(int)segment] = segmentDescriptor.Base;

                    if(segment == SegmentIndex.CS)
                    {
                        if((segmentDescriptor.Attributes2 & SegmentDescriptor.BigMode) == 0)
                            this.Processor.GlobalSize = 0;
                        else
                            this.Processor.GlobalSize = 3;
                    }
                    else if(segment == SegmentIndex.SS)
                    {
                        this.BigStackPointer = (segmentDescriptor.Attributes2 & SegmentDescriptor.BigMode) != 0;
                        this.Processor.TemporaryInterruptMask = true;
                    }
                }
            }
        }
        /// <summary>
        /// Performs a task switch.
        /// </summary>
        /// <param name="selector">Task state segment to switch to.</param>
        /// <param name="clearBusyFlag">Value indicating whether the current task's busy tag should be cleared.</param>
        /// <param name="nestedTaskFlag">Value indicating whether the nested task flag should be set, cleared, or unchanged.</param>
        internal void TaskSwitch32(ushort selector, bool clearBusyFlag, bool? nestedTaskFlag)
        {
            if(selector == 0)
                throw new ArgumentException("selector");

            var p = this.Processor;

            unsafe
            {
                var newDesc = (TaskSegmentDescriptor)this.PhysicalMemory.GetDescriptor(selector);
                var tss = (TaskStateSegment32*)this.PhysicalMemory.GetSafePointer(newDesc.Base, (uint)sizeof(TaskStateSegment32));

                var oldSelector = this.PhysicalMemory.TaskSelector;
                var oldDesc = (TaskSegmentDescriptor)this.PhysicalMemory.GetDescriptor(oldSelector);
                var oldTSS = (TaskStateSegment32*)this.PhysicalMemory.GetSafePointer(oldDesc.Base, (uint)sizeof(TaskStateSegment32));

                oldTSS->CS = p.CS;
                oldTSS->SS = p.SS;
                oldTSS->DS = p.DS;
                oldTSS->ES = p.ES;
                oldTSS->FS = p.FS;
                oldTSS->GS = p.GS;

                oldTSS->EIP = p.EIP;
                oldTSS->ESP = p.ESP;
                oldTSS->EAX = (uint)p.EAX;
                oldTSS->EBX = (uint)p.EBX;
                oldTSS->ECX = (uint)p.ECX;
                oldTSS->EDX = (uint)p.EDX;
                oldTSS->EBP = p.EBP;
                oldTSS->ESI = p.ESI;
                oldTSS->EDI = p.EDI;
                oldTSS->EFLAGS = p.Flags;
                oldTSS->CR3 = p.CR3;
                oldTSS->LDTR = this.PhysicalMemory.LDTSelector;

                if(nestedTaskFlag == false)
                    oldTSS->EFLAGS &= ~EFlags.NestedTask;

                p.CR3 = tss->CR3;
                this.PhysicalMemory.DirectoryAddress = tss->CR3;
                this.PhysicalMemory.LDTSelector = tss->LDTR;

                WriteSegmentRegister(SegmentIndex.CS, tss->CS);
                WriteSegmentRegister(SegmentIndex.SS, tss->SS);
                WriteSegmentRegister(SegmentIndex.DS, tss->DS);
                WriteSegmentRegister(SegmentIndex.ES, tss->ES);
                WriteSegmentRegister(SegmentIndex.FS, tss->FS);
                WriteSegmentRegister(SegmentIndex.GS, tss->GS);

                p.EIP = tss->EIP;
                p.ESP = tss->ESP;
                p.EAX = (int)tss->EAX;
                p.EBX = (int)tss->EBX;
                p.ECX = (int)tss->ECX;
                p.EDX = (int)tss->EDX;
                p.EBP = tss->EBP;
                p.ESI = tss->ESI;
                p.EDI = tss->EDI;
                p.Flags = tss->EFLAGS | EFlags.Reserved1;

                if(nestedTaskFlag == true)
                {
                    p.Flags |= EFlags.NestedTask;
                    tss->LINK = oldSelector;
                }

                if(clearBusyFlag)
                    oldDesc.IsBusy = false;

                newDesc.IsBusy = true;

                this.PhysicalMemory.SetDescriptor(oldSelector, oldDesc);
                this.PhysicalMemory.SetDescriptor(selector, newDesc);
            }

            this.PhysicalMemory.TaskSelector = selector;
        }
        /// <summary>
        /// Returns from a nested task.
        /// </summary>
        internal void TaskReturn()
        {
            unsafe
            {
                var selector = this.PhysicalMemory.TaskSelector;
                var desc = (TaskSegmentDescriptor)this.PhysicalMemory.GetDescriptor(selector);
                var tss = (TaskStateSegment32*)this.PhysicalMemory.GetSafePointer(desc.Base, (uint)sizeof(TaskStateSegment32));
                TaskSwitch32(tss->LINK, true, false);
            }
        }
        /// <summary>
        /// Returns the stack selector of the specified privilege level.
        /// </summary>
        /// <param name="privilegeLevel">Requested privilege level.</param>
        /// <param name="wordSize">Protected mode word size.</param>
        /// <returns>Stack selector of the specified privilege level.</returns>
        internal ushort GetPrivilegedSS(uint privilegeLevel, uint wordSize)
        {
            ushort tss = this.PhysicalMemory.TaskSelector;
            if(tss == 0)
                throw new InvalidOperationException();

            var segmentDescriptor = (SegmentDescriptor)this.PhysicalMemory.GetDescriptor(tss);
            unsafe
            {
                return *(ushort*)this.PhysicalMemory.GetSafePointer(segmentDescriptor.Base + (wordSize * 2u) + (privilegeLevel * (wordSize * 2u)), 2u);
                //return *(ushort*)(this.PhysicalMemory.RawView + segmentDescriptor.Base + (wordSize * 2u) + (privilegeLevel * (wordSize * 2u)));
            }
        }
        /// <summary>
        /// Returns the stack pointer of the specified privilege level.
        /// </summary>
        /// <param name="privilegeLevel">Requested privilege level.</param>
        /// <param name="wordSize">Protected mode word size.</param>
        /// <returns>Stack pointer of the specified privilege level.</returns>
        internal uint GetPrivilegedESP(uint privilegeLevel, uint wordSize)
        {
            ushort tss = this.PhysicalMemory.TaskSelector;
            if(tss == 0)
                throw new InvalidOperationException();

            var segmentDescriptor = (SegmentDescriptor)this.PhysicalMemory.GetDescriptor(tss);
            unsafe
            {
                byte* ptr = (byte*)this.PhysicalMemory.GetSafePointer(segmentDescriptor.Base + wordSize + (privilegeLevel * (wordSize * 2u)), wordSize);
                //byte* ptr = this.PhysicalMemory.RawView + segmentDescriptor.Base + wordSize + (privilegeLevel * (wordSize * 2u));
                if(wordSize == 4u)
                    return *(uint*)ptr;
                else
                    return *(ushort*)ptr;
            }
        }
        #endregion

        #region Public Fields
        /// <summary>
        /// The emulated physical memory of the virtual machine.
        /// </summary>
        [CLSCompliant(false)]
        public readonly PhysicalMemory PhysicalMemory = new PhysicalMemory();
        /// <summary>
        /// The emulated processor of the virtual machine.
        /// </summary>
        [CLSCompliant(false)]
        public readonly Processor Processor = new Processor();
        /// <summary>
        /// The emulated programmable interrupt controller of the virtual machine.
        /// </summary>
        public readonly InterruptController InterruptController = new InterruptController();
        /// <summary>
        /// The emulated programmable interval timer of the virtual machine.
        /// </summary>
        public readonly InterruptTimer InterruptTimer;
        #endregion

        #region Private Fields
        private readonly IInterruptHandler[] interruptHandlers = new IInterruptHandler[256];
        private readonly Dos.DosHandler dos;
        private readonly Video.VideoHandler video;
        private readonly Keyboard.KeyboardDevice keyboard;
        private readonly Mouse.MouseHandler mouse;
        private readonly SortedList<uint, IInputPort> inputPorts = new SortedList<uint, IInputPort>();
        private readonly SortedList<uint, IOutputPort> outputPorts = new SortedList<uint, IOutputPort>();
        private readonly DefaultPortHandler defaultPortHandler = new DefaultPortHandler();
        private readonly FileSystem fileSystem = new FileSystem();
        private readonly List<IVirtualDevice> allDevices = new List<IVirtualDevice>();
        private readonly EnvironmentVariables environmentVariables = new EnvironmentVariables();
        private readonly DmaController dma;
        private readonly Memory.ExpandedMemoryManager emm;
        private readonly Memory.ExtendedMemoryManager xmm;
        private readonly List<DmaChannel> dmaDeviceChannels = new List<DmaChannel>();
        private readonly SortedList<uint, ICallbackProvider> callbackProviders = new SortedList<uint, ICallbackProvider>();
        private readonly ConsoleInStream consoleInStream;
        private readonly ConsoleOutStream consoleOutStream;
        private readonly VirtualConsole console;
        private readonly Interrupts.MultiplexInterruptHandler multiplexHandler;
        private readonly SystemStack stack16;
        private readonly SystemStack stack32;
        private SystemStack stack;
        private bool disposed;
        private bool showMouse;
        private bool showCursor = true;
        #endregion
    }
}
